/*
Copyright 2025.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import (
	"bufio"
	"context"
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"

	"golang.org/x/crypto/ssh"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"

	opsv1 "ops-operator/api/v1"

	batchv1 "k8s.io/api/batch/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	corev1 "k8s.io/api/core/v1"
)

// TaskReconciler reconciles a Task object
type TaskReconciler struct {
	client.Client
	Scheme *runtime.Scheme
}

// +kubebuilder:rbac:groups=ops.example.com,resources=tasks,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=ops.example.com,resources=tasks/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=ops.example.com,resources=tasks/finalizers,verbs=update
// +kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=batch,resources=cronjobs/status,verbs=get;update;patch

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Task object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.0/pkg/reconcile
func (r *TaskReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	_ = log.FromContext(ctx)

	var task opsv1.Task
	if err := r.Get(ctx, req.NamespacedName, &task); err != nil {
		if apierrors.IsNotFound(err) {
			return ctrl.Result{}, nil
		}
		return ctrl.Result{}, err
	}

	if task.Spec.Schedule == "" {
		if task.Status.State == "Running" || task.Status.State == "Completed" {
			return ctrl.Result{}, nil
		}
		//设置任务状态为运行中
		task.Status.State = "Running"
		task.Status.LastRunTime = time.Now().Format("2006-01-02 15:04:05")
		if err := r.Status().Update(ctx, &task); err != nil {
			return ctrl.Result{}, err
		}
		result, err := r.executeTask(ctx, &task)
		if err != nil {
			task.Status.State = "Failed"
			task.Status.Result = err.Error()
		} else {
			task.Status.State = "Completed"
			task.Status.Result = result
		}
		//更新任务状态
		if err := r.Status().Update(ctx, &task); err != nil {
			return ctrl.Result{}, err
		}
	} else {
		cronjob := createCronjob(&task, task.Spec.Command, task.Namespace)
		//设置controller引用
		if err := ctrl.SetControllerReference(&task, cronjob, r.Scheme); err != nil {
			return ctrl.Result{}, fmt.Errorf("设置controller引用失败: %v", err)
		}

		//检查cronjob是否存在
		existingCronjob := &batchv1.CronJob{}
		if err := r.Get(ctx, client.ObjectKeyFromObject(cronjob), existingCronjob); err != nil {
			if apierrors.IsNotFound(err) {
				if err := r.Create(ctx, cronjob); err != nil {
					// 重新获取最新的task对象
					if err := r.Get(ctx, req.NamespacedName, &task); err != nil {
						return ctrl.Result{}, err
					}
					task.Status.State = "Failed"
					task.Status.Result = fmt.Sprintf("创建cronjob失败: %v", err)
				} else {
					if err := r.Get(ctx, req.NamespacedName, &task); err != nil {
						return ctrl.Result{}, err
					}
					task.Status.State = "Completed"
					task.Status.Result = "cronjob创建成功"
				}
			} else {
				if err := r.Get(ctx, req.NamespacedName, &task); err != nil {
					return ctrl.Result{}, err
				}
				task.Status.State = "Failed"
				task.Status.Result = fmt.Sprintf("获取cronjob失败: %v", err)
			}
		} else {
			err := r.Update(ctx, cronjob)
			if err != nil {
				if err := r.Get(ctx, req.NamespacedName, &task); err != nil {
					return ctrl.Result{}, err
				}
				task.Status.State = "Failed"
				task.Status.Result = fmt.Sprintf("更新cronjob失败: %v", err)
			} else {
				//更新最近一次job运行结果
				if err := r.Get(ctx, req.NamespacedName, &task); err != nil {
					return ctrl.Result{}, err
				}
				if err := r.updateTaskResult(ctx, &task); err != nil {
					task.Status.State = "Failed"
					task.Status.Result = fmt.Sprintf("更新任务状态失败: %v", err)
				}
			}
		}

		//更新任务状态
		if err := r.Status().Update(ctx, &task); err != nil {
			return ctrl.Result{}, err
		}
	}

	return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *TaskReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&opsv1.Task{}).
		Owns(&batchv1.CronJob{}).
		Named("task").
		Complete(r)
}

// executeTask 执行任务
func (r *TaskReconciler) executeTask(ctx context.Context, task *opsv1.Task) (string, error) {
	sshconfig := &SSHConfig{
		Host: task.Spec.Ip,
		Port: task.Spec.Port,
		User: task.Spec.Username,
		// PrivateKey: task.Spec.PrivateKey,
		PrivateKeySecretRef: task.Spec.PrivateKeySecretRef,
	}

	var command string
	if len(task.Spec.Args) > 0 {
		command = task.Spec.Command + " " + strings.Join(task.Spec.Args, " ")
	} else {
		command = task.Spec.Command
	}

	result, err := r.remote_cmd(ctx, sshconfig, command, task.Namespace)
	if err != nil {
		return "", err
	}

	return result, nil
}

// SSHConfig 包含 SSH 连接所需的信息
type SSHConfig struct {
	Host string
	Port int
	User string
	//cat /root/.ssh/id_rsa |base64 -w 0生成私钥base64编码
	// PrivateKey string
	PrivateKeySecretRef *corev1.SecretKeySelector
}

// remote_cmd 执行远程命令
func (r *TaskReconciler) remote_cmd(ctx context.Context, sshconfig *SSHConfig, cmd string, namespace string) (string, error) {
	var auth []ssh.AuthMethod
	var result string
	// 解析私钥
	// decodekey, err := base64.StdEncoding.DecodeString(sshconfig.PrivateKey)
	// 获取私钥
	secret := &corev1.Secret{}
	if err := r.Get(ctx, client.ObjectKey{Name: sshconfig.PrivateKeySecretRef.Name, Namespace: namespace}, secret); err != nil {
		return "", err
	}
	// 解码私钥
	decodekey, ok := secret.Data[sshconfig.PrivateKeySecretRef.Key]
	if !ok {
		return "", errors.New("private key not found")
	}
	// 解析私钥
	key, err := ssh.ParsePrivateKey(decodekey)
	if err != nil {
		return "", err
	}
	// 添加认证方法
	auth = append(auth, ssh.PublicKeys(key))
	// 配置SSH客户端
	config := &ssh.ClientConfig{
		User:            sshconfig.User,
		Auth:            auth,
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", sshconfig.Host, sshconfig.Port), config)
	if err != nil {
		return "", err
	}
	defer client.Close()

	// 创建会话
	session, err := client.NewSession()
	if err != nil {
		return "", err
	}
	defer session.Close()

	// 获取标准输出
	stdout, err := session.StdoutPipe()
	if err != nil {
		return "", err
	}

	// 执行命令
	if err := session.Run(cmd); err != nil {
		return "", err
	}

	// 读取标准输出
	scanner := bufio.NewScanner(stdout)
	for scanner.Scan() {
		result += scanner.Text() + "\n"
	}

	return result, nil
}

// 创建kubernetes cronjob
func createCronjob(task *opsv1.Task, cmd string, namespace string) *batchv1.CronJob {
	return &batchv1.CronJob{
		ObjectMeta: metav1.ObjectMeta{
			Name:      task.Name,
			Namespace: namespace,
		},
		Spec: batchv1.CronJobSpec{
			Schedule: task.Spec.Schedule,
			JobTemplate: batchv1.JobTemplateSpec{
				//设置job的标签
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"cronjob": task.Name,
					},
				},
				Spec: batchv1.JobSpec{
					Template: corev1.PodTemplateSpec{
						//设置pod的标签
						ObjectMeta: metav1.ObjectMeta{
							Labels: map[string]string{
								"cronjob": task.Name,
							},
						},
						Spec: corev1.PodSpec{
							RestartPolicy: corev1.RestartPolicyNever,
							Containers: []corev1.Container{
								{
									Name:  task.Name,
									Image: "myssh:v1.0",
									Command: []string{
										"sh", "-c",
										"cp /root/.ssh/id_rsa /tmp/id_rsa && chmod 600 /tmp/id_rsa && output=$(ssh -i /tmp/id_rsa -p " + strconv.Itoa(task.Spec.Port) + " -o StrictHostKeyChecking=no " + task.Spec.Username + "@" + task.Spec.Ip + " " + "'" + cmd + "' 2>&1) && echo \"COMMAND OUTPUT: $output\"",
									},
									VolumeMounts: []corev1.VolumeMount{
										{
											Name:      "ssh-key",
											MountPath: "/root/.ssh/id_rsa",
											SubPath:   "id_rsa",
										},
									},
								},
							},
							Volumes: []corev1.Volume{
								{
									Name: "ssh-key",
									VolumeSource: corev1.VolumeSource{
										Secret: &corev1.SecretVolumeSource{
											SecretName: task.Spec.PrivateKeySecretRef.Name,
											Items: []corev1.KeyToPath{
												{
													Key:  "id_rsa",
													Path: "id_rsa",
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
		},
	}
}

// 从cronjob中获取任务结果
func (r *TaskReconciler) updateTaskResult(ctx context.Context, task *opsv1.Task) error {
	var joblist batchv1.JobList
	//获取指定cronjob的job
	if err := r.List(ctx, &joblist, client.InNamespace(task.Namespace), client.MatchingLabels{"cronjob": task.Name}); err != nil {
		return fmt.Errorf("获取job列表失败: %v", err)
	}

	//获取最近一次的job的状态
	sort.Slice(joblist.Items, func(i, j int) bool {
		return joblist.Items[i].CreationTimestamp.After(joblist.Items[j].CreationTimestamp.Time)
	})
	if len(joblist.Items) == 0 {
		return fmt.Errorf("job列表为空")
	}
	//获取job的执行状态,获取job的执行时间
	latestJob := joblist.Items[0]
	if latestJob.Status.Succeeded > 0 {
		task.Status.State = "Completed"
		task.Status.Result = "Latest Job Completed"
		task.Status.LastRunTime = latestJob.CreationTimestamp.Format("2006-01-02 15:04:05")
	} else if latestJob.Status.Failed > 0 {
		task.Status.State = "Failed"
		task.Status.Result = "Latest Job Failed"
		task.Status.LastRunTime = latestJob.CreationTimestamp.Format("2006-01-02 15:04:05")
	} else {
		task.Status.State = "Running"
		task.Status.Result = "Latest Job Running"
		task.Status.LastRunTime = latestJob.CreationTimestamp.Format("2006-01-02 15:04:05")
	}

	return nil
}
